/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.cef.generator.coreGenerator.base.dualaccgen.method;

import com.inspur.edp.caf.generator.baseInfo.ParameterInfo;
import com.inspur.edp.caf.generator.baseInfo.TypeInfo;
import com.inspur.edp.caf.generator.method.ClassMethodGenerator;
import com.inspur.edp.caf.generator.utils.ExpressionUtils;
import com.inspur.edp.cef.core.data.AccessorCollection;
import com.inspur.edp.cef.core.data.AccessorReadonlyCollection;
import com.inspur.edp.cef.entity.accessor.entity.IChildAccessor;
import com.inspur.edp.cef.entity.accessor.entity.IEntityAccessor;
import com.inspur.edp.cef.entity.entity.IChildEntityData;
import com.inspur.edp.cef.gencommon.MediateType;
import com.inspur.edp.cef.generator.coreGenerator.base.accessorgen.AccessorUtil;
import com.inspur.edp.cef.generator.coreGenerator.base.entitygen.EntityGeneratorContext;
import com.inspur.edp.cef.generator.coreGenerator.base.entitygen.method.GetResInfoGenerator;
import com.inspur.edp.cef.generator.overall.GeneratorUtil;
import com.inspur.edp.cef.generator.repository.RepositoryGenUtil;
import com.inspur.edp.das.commonmodel.IGspCommonObject;
import java.util.ArrayList;
import java.util.function.Function;
import java.util.function.Supplier;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;

public class DualNewChildCollectionGenerator extends ClassMethodGenerator {

  private static String ParName_belong = "belong";
  private final IGspCommonObject node;
  private final EntityGeneratorContext parentContext;

  public DualNewChildCollectionGenerator(EntityGeneratorContext parentContext,
      IGspCommonObject node) {
    this.node = node;
    this.parentContext = parentContext;
  }

  public static String getMethodName(IGspCommonObject node) {
    return "new" + node.getCode() + "Collection";
  }

  @Override
  protected String getMethodName() {
    return getMethodName(node);
  }

  @Override
  protected TypeInfo getReturnType() {
    return new TypeInfo(AccessorCollection.class);
  }

  @Override
  protected ArrayList<ModifierKeyword> getAccessModifiers() {
    return RepositoryGenUtil.createModifierKeywordArray(ModifierKeyword.PRIVATE_KEYWORD);
  }

  @Override
  protected ArrayList<ParameterInfo> getParameterCollection() {
    ArrayList<ParameterInfo> rez = new ArrayList<>();
    rez.add(GeneratorUtil.createParameterInfo(IEntityAccessor.class, ParName_belong));
    return rez;
  }

  @Override
  protected Block buildMethodBody() {
    addImport(IChildEntityData.class.getTypeName());
    addImport(IChildAccessor.class.getTypeName());
    addImport(AccessorReadonlyCollection.class.getTypeName());
    Block block = ast.newBlock();
    String dataSupplierName = buildDataSupplierPar(block);
    String accSupplierName = buildAccSupplierPar(block);

    IfStatement ifStatement = ast.newIfStatement();
    ifStatement.setExpression(buildIfCondition());
    ifStatement
        .setThenStatement(buildAccCollectionCreation(dataSupplierName, accSupplierName, true));
    ifStatement
        .setElseStatement(buildAccCollectionCreation(dataSupplierName, accSupplierName, false));
    block.statements().add(ifStatement);
    return block;
  }

  private Statement buildAccCollectionCreation(String dataSupplierName, String accSupplierName,
      boolean isReadonly) {
    Block block = ast.newBlock();
    ClassInstanceCreation classInstanceCreation = ast.newClassInstanceCreation();
    classInstanceCreation.setType(
        new TypeInfo(isReadonly ? AccessorReadonlyCollection.class : AccessorCollection.class)
            .getType(ast));
    classInstanceCreation.arguments().add(buildBelongEntityPar());
    classInstanceCreation.arguments().add(buildResInfoPar());
    classInstanceCreation.arguments().add(ast.newSimpleName(dataSupplierName));
    classInstanceCreation.arguments().add(ast.newSimpleName(accSupplierName));
    ReturnStatement returnStatement = ast.newReturnStatement();
    returnStatement.setExpression(classInstanceCreation);
    block.statements().add(returnStatement);
    return block;
  }

  private Expression buildIfCondition() {
    MethodInvocation methodInvocation = ast.newMethodInvocation();
    methodInvocation.setName(ast.newSimpleName(AccessorUtil.MethodName_AccIsReadonly));
    return methodInvocation;
  }

  //Function<IChildEntityData, IChildAccessor>
  private String buildAccSupplierPar(Block block) {
    final String accSupplierName = "accSupplier";

    ClassInstanceCreation classInstanceCreation = ast.newClassInstanceCreation();

    ParameterizedType supplierType = ast.newParameterizedType(
        new TypeInfo(Function.class).getType(ast, parentContext.getProjContext()));
    supplierType.typeArguments().add(new TypeInfo(IChildEntityData.class).getType(ast));
    supplierType.typeArguments().add(new TypeInfo(IChildAccessor.class).getType(ast));
    classInstanceCreation.setType(supplierType);

    AnonymousClassDeclaration classDeclaration = ast.newAnonymousClassDeclaration();
    classDeclaration.bodyDeclarations();
    MethodDeclaration methodDeclaration = ast.newMethodDeclaration();
    methodDeclaration.setName(ast.newSimpleName("apply"));
    methodDeclaration.modifiers().add(ast.newModifier(ModifierKeyword.PUBLIC_KEYWORD));
    methodDeclaration.setReturnType2(new TypeInfo(IChildAccessor.class).getType(ast));
    SingleVariableDeclaration applyPar = ast.newSingleVariableDeclaration();
    applyPar.setType(new TypeInfo(IChildEntityData.class).getType(ast));
    String applyParName = "childData";
    applyPar.setName(ast.newSimpleName(applyParName));
    methodDeclaration.parameters().add(applyPar);
    Block methodBlock = ast.newBlock();
    ReturnStatement returnStatement = ast.newReturnStatement();

    MediateType accType = parentContext.getProjContext().getAccessorMediateTypes()
        .get(node.getCode());
    ClassInstanceCreation accClassInstanceCreation = ast.newClassInstanceCreation();
    accClassInstanceCreation.setType(ast.newSimpleType(ast.newName(accType.getFullName())));
    CastExpression castExpression = ast.newCastExpression();
    castExpression.setType(ast.newSimpleType(ast.newName(
        parentContext.getProjContext().getCefGenContext().getDataInterfaceTypes()
            .get(node.getCode()).getFullName())));
    castExpression.setExpression(ast.newSimpleName(applyParName));
    accClassInstanceCreation.arguments().add(castExpression);
    accClassInstanceCreation.arguments().add(RepositoryGenUtil
        .createMethodInvocation(ast, null, AccessorUtil.MethodName_AccIsReadonly, null));

    returnStatement.setExpression(accClassInstanceCreation);
    methodBlock.statements().add(returnStatement);
    methodDeclaration.setBody(methodBlock);
    classDeclaration.bodyDeclarations().add(methodDeclaration);
    classInstanceCreation.setAnonymousClassDeclaration(classDeclaration);

    VariableDeclarationFragment variableDeclarationFragment = ast.newVariableDeclarationFragment();
    variableDeclarationFragment.setName(ast.newSimpleName(accSupplierName));
    variableDeclarationFragment.setInitializer(classInstanceCreation);
    VariableDeclarationStatement variableDeclarationStatement = ast
        .newVariableDeclarationStatement(variableDeclarationFragment);

    ParameterizedType supplierType1 = ast.newParameterizedType(
        new TypeInfo(Function.class).getType(ast));
    supplierType1.typeArguments().add(new TypeInfo(IChildEntityData.class).getType(ast));
    supplierType1.typeArguments().add(new TypeInfo(IChildAccessor.class).getType(ast));
    variableDeclarationStatement.setType(supplierType1);
    block.statements().add(variableDeclarationStatement);
    return accSupplierName;
  }

  private String buildDataSupplierPar(Block block) {
    final String dataSupplierName = "dataSupplier";

    ClassInstanceCreation classInstanceCreation = ast.newClassInstanceCreation();

    ParameterizedType supplierType = ast.newParameterizedType(
        new TypeInfo(Supplier.class).getType(ast, parentContext.getProjContext()));
    supplierType.typeArguments().add(new TypeInfo(IChildEntityData.class).getType(ast));
    classInstanceCreation.setType(supplierType);

    AnonymousClassDeclaration classDeclaration = ast.newAnonymousClassDeclaration();
    classDeclaration.bodyDeclarations();
    MethodDeclaration methodDeclaration = ast.newMethodDeclaration();
    methodDeclaration.setName(ast.newSimpleName("get"));
    methodDeclaration.modifiers().add(ast.newModifier(ModifierKeyword.PUBLIC_KEYWORD));
    methodDeclaration.setReturnType2(new TypeInfo(IChildEntityData.class).getType(ast));
    Block methodBlock = ast.newBlock();
    ReturnStatement returnStatement = ast.newReturnStatement();
    ClassInstanceCreation instanceCreation = ast.newClassInstanceCreation();
    instanceCreation.setType(ast.newSimpleType(ast.newName(
        parentContext.getProjContext().getEntityDataMediateTypes().get(node.getCode())
            .getFullName())));
    returnStatement.setExpression(instanceCreation);
    methodBlock.statements().add(returnStatement);
    methodDeclaration.setBody(methodBlock);
    classDeclaration.bodyDeclarations().add(methodDeclaration);
    classInstanceCreation.setAnonymousClassDeclaration(classDeclaration);

    VariableDeclarationFragment variableDeclarationFragment = ast.newVariableDeclarationFragment();
    variableDeclarationFragment.setName(ast.newSimpleName(dataSupplierName));
    variableDeclarationFragment.setInitializer(classInstanceCreation);
    VariableDeclarationStatement variableDeclarationStatement = ast
        .newVariableDeclarationStatement(variableDeclarationFragment);

    ParameterizedType supplierType1 = ast.newParameterizedType(
        new TypeInfo(Supplier.class).getType(ast));
    supplierType1.typeArguments().add(new TypeInfo(IChildEntityData.class).getType(ast));
    variableDeclarationStatement.setType(supplierType1);
    block.statements().add(variableDeclarationStatement);
    return dataSupplierName;
  }

  private Object buildResInfoPar() {
    MediateType entityType = parentContext.getProjContext().getEntityMediateTypes()
        .get(node.getCode());
    MethodInvocation invocation = ExpressionUtils
        .getMethodInvocation(ast, entityType.getFullName(), GetResInfoGenerator.MethodName);
    return invocation;
  }

  private SimpleName buildBelongEntityPar() {
    return ast.newSimpleName(ParName_belong);
  }
}
